package pl;

import javacard.framework.Util;

public abstract class BERTLV {

	boolean emptyTLV;
	short numDeletedTLVs;
	short numTLVs;
	BERTag theBERTag;
	byte[] theValue;
	
	short valueLength;
	short valueOffset;

	protected BERTLV() {
		this.emptyTLV = true;
		this.numDeletedTLVs = 0;
		this.theBERTag = null;
		this.valueLength = 0;
	}

	public abstract short init(byte[] bArray, short bOff, short bLen);

	public short getLength() {
		return valueLength;
	}

	static ConstructedBERTLV prev;
	static BERTag berTag;
	
	public static BERTLV getInstance(byte[] bArray, short bOff, short bLen) {
		//BERTag berTag;
		BERTLV tlv = null;

		if (bArray == null)
			throw new NullPointerException();
		else {
			if (true)// verifyFormat(bArray, bOff, bLen))
			{
				if (bOff < (short) bArray.length) {
					berTag = BERTag.getInstance(bArray, bOff);
					bOff += berTag.size();
					short size = getLength(bArray, bOff);

					if (berTag.isConstructed()) {
						tlv = new ConstructedBERTLV((short) 8);
						tlv.theBERTag = berTag;
						prev = (ConstructedBERTLV) tlv;
						prev.theBERTag = BERTag.getInstance(new byte[]{(byte)0xA0}, (short)0);
						
						// ((ConstructedBERTLV)tlv).berTlv[0] = tlv;
						// get length ->
					} else {
						tlv = new PrimitiveBERTLV(size);
						tlv.theBERTag = berTag;
					}

					// length

					if (size < 128)
						bOff++;
					else if (size > 127)
						bOff += 2;
					else if (size > 255)
						bOff += 3;
					tlv.valueOffset = bOff;
					tlv.init(bArray, bOff, (short) (size)); // bLen - tag.size()
				    
					if (tlv instanceof PrimitiveBERTLV) 
						prev.berTlv[prev.findFirstEmptySlot()] = tlv;	
													// - l
				}
			}
		}
		return tlv;
	}

	/*
	 * getInstance(bArray, bOff, bLen = None): if bLen is None: # This is a
	 * trick of mine, the JC framework will anyway always # call it will three
	 * arguments bLen = BERTLV.getLength(bArray, bOff) tag =
	 * BERTag.getInstance(bArray, bOff) if tag.isConstructed(): tlv =
	 * ConstructedBERTLV(0) else: tlv = PrimitiveBERTLV(0) bOff += tag.size() l,
	 * length = _getLength(bArray[bOff:]) # this calls the init method of the
	 * child class, not BERTLV tlv.init(tag, bArray, bOff + l, bLen - tag.size()
	 * - l) return tlv
	 */

	// TODO: Test
	public static short getLength(byte[] berTLVArray, short bOff) {
		if (berTLVArray == null)
			throw new NullPointerException();

		if (((byte) berTLVArray[bOff] & 0x80) != 0x80) {
			// offset++;
			return berTLVArray[bOff];
			// offset++;
		} else {
			if ((berTLVArray[bOff] & 0x03) == 0x01) {
				// offset+=2;
				return (short) (berTLVArray[(short) (bOff + 1)] & 0x00FF);
			} else if (((berTLVArray[bOff] & 0x03) == 0x02)) {
				// offset+=3;
				return Utils.byteArrayToShort(new byte[] {
						berTLVArray[(short) (bOff + 2)],
						berTLVArray[(short) (bOff + 1)] });
			}
		}

		return -1;
	}

	// TODO: Implementation
	public static short getTag(byte[] berTLVArray, short bTLVOff,
			byte[] berTagArray, short bTagOff) {
		return 0;
	}

	// OK
	public BERTag getTag() {
		return theBERTag;
	}

	public short size() {
		short value = 0;
		value += this.theBERTag.size();

		if (this instanceof PrimitiveBERTLV) {
			// length
			if (valueLength < 128)
				value++;
			else if (valueLength > 127 && valueLength < 256 )
				value += 2;
			else if (valueLength > 255)
				value += 3;

			// value
			value += getLength();
		}
		return value;
	}

	public short toBytes(byte[] outBuf, short bOff) {
		return 0;
	}

	public static boolean verifyFormat(byte[] berTlvArray, short bOff,
			short bLen) throws NullPointerException,
			ArrayIndexOutOfBoundsException {
		if (berTlvArray == null)
			throw new NullPointerException();

		if ((short) (berTlvArray.length + bOff) >= bLen) {
			// tag
			if ((berTlvArray[bOff] & 0x1F) == 0x1F)
				bOff += 2; // 2 b
			else
				bOff++; // 1 b

			// length //9F 66 81 00 =?
			short checkBit = (short) (berTlvArray[bOff] & 0x80);

			if (checkBit == 0x00) {
				bOff++;
				if (((byte) (bLen - bOff)) == (byte) berTlvArray[(short) (bOff - 1)])
					return true;
				else
					return false;
			} else {
				checkBit = (short) (berTlvArray[bOff] & 0x03);
				bOff++;
				switch (checkBit) {
				case 1:
					// 81 81
					if ((berTlvArray[(short) bOff] & 0x80) == 0x80) {
						checkBit = (short) (berTlvArray[(short) (bOff)] & 0x00FF);
						if (checkBit < 128)
							return false;

						bOff++;

						if ((short) (berTlvArray.length - checkBit) != bOff)
							return false;
					} else
						return false;
					break;

				case 2:
					checkBit = Utils.byteArrayToShort(new byte[] {
							berTlvArray[(short) (bOff + 1)],
							berTlvArray[(short) (bOff)] });

					if (checkBit < 256)
						return false;
					bOff += 2;
					if ((short) (berTlvArray.length - checkBit) != bOff)
						return false;
					break;

				default:
					return false;
				}

				return true;
			}
		} else
			throw new ArrayIndexOutOfBoundsException();

	}

	public static byte[] getTLVLength(short value) {
		byte[] abc;

		if (value < 128)
			return new byte[] { (byte) value };

		else if (value > 127 && value < 256)
			return new byte[] { (byte) 0x81,
					(byte) ((byte) (value % 128) | 0x80) };

		else if (value > 255 && value < (short) 0xFFFE) {
			abc = new byte[3];
			abc[0] = (byte) 0x82;
			Util.arrayCopy(Utils.shortOnByteArray(value), (short) 0, abc,
					(short) 1, (short) 2);
			return abc;
		} else {
			throw new NullPointerException();
		}
	}

	// 30 10 30 06 80 02 0
	public static BERTLV getInstance2(BERTLV tlv, byte[] bArray, short bOff,
			short bLen) {
		if (bLen == bOff)
			return tlv;
		else {
			BERTag berTag = BERTag.getInstance(bArray, bOff);

			if (berTag.isConstructed()) {
				if (tlv == null)
					tlv = new ConstructedBERTLV((short) 32);
				else {
					// tlv
				}

			} else {
				if (tlv == null)
					tlv = new PrimitiveBERTLV(bLen);
				else {

				}
				// detect value length
				// init primitive BERTLV
			}
		}
		return tlv;
	}
}
